package com.example.testdemo.scenes.stage;

import java.util.List;
import java.util.Random;

import org.andengine.engine.camera.ZoomCamera;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl;
import org.andengine.engine.camera.hud.controls.BaseOnScreenControl.IOnScreenControlListener;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.IEntity;
import org.andengine.entity.modifier.MoveModifier;
import org.andengine.entity.modifier.PathModifier;
import org.andengine.entity.modifier.PathModifier.IPathModifierListener;
import org.andengine.entity.modifier.PathModifier.Path;
import org.andengine.entity.scene.background.Background;
import org.andengine.entity.sprite.ButtonSprite;
import org.andengine.entity.sprite.Sprite;
import org.andengine.extension.tmx.TMXLayer;
import org.andengine.extension.tmx.TMXObject;
import org.andengine.extension.tmx.TMXTile;
import org.andengine.util.color.Color;
import org.andengine.util.modifier.IModifier;
import org.andengine.util.modifier.IModifier.IModifierListener;

import android.util.Log;
import android.view.KeyEvent;

import com.example.testdemo.TankGameActivity;
import com.example.testdemo.controllers.FcController;
import com.example.testdemo.controllers.FcController.IOnButtonPressHoldListener;
import com.example.testdemo.kinetic.interfaces.IOnDestroyListener;
import com.example.testdemo.kinetic.interfaces.IOnDestroyTargetListener;
import com.example.testdemo.kinetic.sprite.bullet.BaseBullet;
import com.example.testdemo.kinetic.sprite.tank.enemy.EnemyTank1;
import com.example.testdemo.kinetic.sprite.tank.player.PlayerTank1;
import com.example.testdemo.kstatic.sprite.ItemSprite;
import com.example.testdemo.kstatic.sprite.terrain.ObstacleTerrain;
import com.example.testdemo.kstatic.sprite.terrain.WallTerrain;
import com.example.testdemo.manager.CamerasManager;
import com.example.testdemo.manager.ResourcesManager;
import com.example.testdemo.manager.ScenesManager;
import com.example.testdemo.pools.BulletPools;
import com.example.testdemo.pools.ItemPools;
import com.example.testdemo.pools.TankPools;
import com.example.testdemo.resources.CharacterResources;
import com.example.testdemo.resources.stage.BaseStageResources;
import com.example.testdemo.scenes.BaseScene;
import com.example.testdemo.scenes.menu.MenuInGameScene;

public abstract class BaseStage extends BaseScene {

    private final String TAG = BaseStage.class.getSimpleName();

    private final float ZOOM_FACTOR = 0.85f;

    private final int MAX_TANKS = 16;
    private final int NUMBER_COME_OUT_PER_TIME = 4;
    private final int FIRST_TIME_COME_OUT_ELAPSED = 5;
    private final int COME_OUT_FREQUENCY = 10;

    protected int mIdResources = -1;
    protected int mIdStage = -1;

    protected TankGameActivity mActivity = null;
    protected BaseStageResources mStageResources = null;

    protected ZoomCamera mCamera;
    protected CharacterResources mCharacterResources;
    protected SpriteTag mSpriteTag;

    protected FcController mFcController;
    protected MenuInGameScene mMenuScene;

    protected BulletPools mBulletPools;
    protected TankPools mTankPools;
    protected TimerHandler mTankFirstTimeGenerator;
    protected TimerHandler mTankGenerator;

    protected ItemPools mItemPools;

    @Override
    public void createScene(TankGameActivity activity) {
        mActivity = activity;
        mCamera = (ZoomCamera) CamerasManager.getInstance().getCamera(
                CamerasManager.CAMERA_ZOOM);
        mStageResources = (BaseStageResources) ResourcesManager.getInstance()
                .getResources(mIdResources);
        mCharacterResources = (CharacterResources) ResourcesManager
                .getInstance().getResources(
                        ResourcesManager.RESOURCES_CHARACTER);

        mSpriteTag = new SpriteTag(mIdStage);

        configureCamera();

        Log.d(TAG, "createScene() successfully!");
    }

    private void configureCamera() {
        final TMXLayer layer = mStageResources.mMap.getTMXLayers().get(0);
        mCamera.setBounds(0, 0, layer.getWidth(), layer.getHeight());
        mCamera.setBoundsEnabled(true);
        mCamera.setZoomFactor(ZOOM_FACTOR);
    }

    @Override
    public void populateScene() {
        createBackground();
        createItems();
        createMenuScene();
        createBulletPools();
        createPlayer();
        createController();
        createTanksGenerator();
        startBackgroundMusic();

        Log.d(TAG, "populateScene() successfully!");
    }

    private void createBackground() {
        setBackground(new Background(0, 0, 0));

        final TMXLayer layer = mStageResources.mMap.getTMXLayers().get(0);
        attachChild(layer);

        // Heart
        Sprite heart = new Sprite(mStageResources.mHeartOnTile.getTileX(),
                mStageResources.mHeartOnTile.getTileY(),
                mStageResources.mHeartOnTile.getTextureRegion(),
                mActivity.getVertexBufferObjectManager());
        heart.setTag(mSpriteTag.HEART);
        attachChild(heart);

        int i = 0;
        // Obstacle
        for (TMXTile tile : mStageResources.mTileObstacleList) {
            // setup sprite
            ObstacleTerrain obstacle = new ObstacleTerrain(tile.getTileX(),
                    tile.getTileY(), tile.getTextureRegion(),
                    mActivity.getVertexBufferObjectManager());
            obstacle.setTag(mSpriteTag.OBSTACLE_START + i);
            attachChild(obstacle);

            i++;
        }

        // Wall
        i = 0;
        for (TMXTile tile : mStageResources.mTileWallList) {
            // setup sprite
            WallTerrain wall = new WallTerrain(tile.getTileX(),
                    tile.getTileY(), tile.getTextureRegion(),
                    mActivity.getVertexBufferObjectManager());
            wall.setTag(mSpriteTag.WALL_START + i);
            attachChild(wall);

            i++;
        }
    }

    private void createItems() {
        mItemPools = new ItemPools(mActivity, mIdStage);
        mItemPools.createItemSprites();
        List<ItemSprite> itemList = mItemPools.getItemList();

        // make half items on low rows and half on high rows
        Random random = new Random(System.currentTimeMillis());
        final float middlePos = mStageResources.mMap.getTileRows() / 2
                * mStageResources.mMap.getTileHeight();
        int lowRegionIndex = 0;
        int heightRegionIndex = itemList.size() / 2;
        for (ItemSprite item : itemList) {
            boolean setToWall = false;
            while (!setToWall) {
                int tag = random.nextInt(mSpriteTag.WALL_END
                        - mSpriteTag.WALL_START)
                        + mSpriteTag.WALL_START;

                WallTerrain wall = (WallTerrain) getChildByTag(tag);
                if (wall.getY() < middlePos
                        && lowRegionIndex < itemList.size() / 2) {
                    lowRegionIndex++;
                    wall.setItemSprite(item);
                    setToWall = true;
                } else if (wall.getY() > middlePos
                        && heightRegionIndex < itemList.size()) {
                    heightRegionIndex++;
                    wall.setItemSprite(item);
                    setToWall = true;
                }
            }
        }
    }

    private void createMenuScene() {
        ResourcesManager.getInstance().loadResources(mActivity,
                ScenesManager.MENU_IN_GAME);
        ScenesManager.getInstance().loadMenu(mActivity,
                ScenesManager.MENU_IN_GAME, mIdStage);
        mMenuScene = (MenuInGameScene) ScenesManager.getInstance().getMenu(
                ScenesManager.MENU_IN_GAME);
    }

    private void createBulletPools() {
        mBulletPools = BulletPools.getInstance(mIdResources);
        mBulletPools.initBullet(mActivity, BulletPools.ENEMY_BULLET_1);
        mBulletPools.initBullet(mActivity, BulletPools.PLAYER_BULLET_1);
    }

    private void createPlayer() {
        final TMXObject objPlayer = mStageResources.mObjPlayerList.get(0);
        final PlayerTank1 player = new PlayerTank1(ScenesManager.SCENE_STAGE_1,
                PlayerTank1.BASE_NAME, objPlayer.getX(), objPlayer.getY() - 3,
                mCharacterResources.mTankPlayer1TextureRegion, mActivity);
        player.walkingAnimation();
        player.setOnDestroyListener(new IOnDestroyListener() {
            @Override
            public void onDestroy(Sprite sprite) {
                if (mFcController != null) {
                    mFcController.delPlayerLife();
                    if (mFcController.getPlayerLives() <= 0) {
                        lose();
                    } else {
                        float xOffset = Math.abs(player.getX()
                                - objPlayer.getX());
                        float yOffset = Math.abs(player.getY()
                                - objPlayer.getY());
                        if (xOffset < 20 && yOffset < 20) {
                            MoveModifier modifier = new MoveModifier(1, player
                                    .getX(), objPlayer.getX(), player.getY(),
                                    objPlayer.getY());
                            modifier.addModifierListener(new IModifierListener<IEntity>() {
                                @Override
                                public void onModifierStarted(
                                        IModifier<IEntity> pModifier,
                                        IEntity pItem) {
                                }

                                @Override
                                public void onModifierFinished(
                                        IModifier<IEntity> pModifier,
                                        IEntity pItem) {
                                    player.setVisible(true);
                                }
                            });
                            player.registerEntityModifier(modifier);
                        } else {
                            float xArr[] = new float[] { player.getX(),
                                    objPlayer.getX() };
                            float yArr[] = new float[] { player.getY(),
                                    objPlayer.getY() - 3 };
                            Path path = new Path(xArr, yArr);
                            PathModifier modifier = new PathModifier(1, path);
                            modifier.setPathModifierListener(new IPathModifierListener() {
                                @Override
                                public void onPathWaypointStarted(
                                        PathModifier pPathModifier,
                                        IEntity pEntity, int pWaypointIndex) {
                                }

                                @Override
                                public void onPathWaypointFinished(
                                        PathModifier pPathModifier,
                                        IEntity pEntity, int pWaypointIndex) {
                                }

                                @Override
                                public void onPathStarted(
                                        PathModifier pPathModifier,
                                        IEntity pEntity) {
                                }

                                @Override
                                public void onPathFinished(
                                        PathModifier pPathModifier,
                                        IEntity pEntity) {
                                    player.setVisible(true);
                                }
                            });
                            player.registerEntityModifier(modifier);
                        }
                    }
                }
            }
        });
        player.setOnDestroyTargetListener(new IOnDestroyTargetListener() {
            @Override
            public void onTargetDestroyed(int type, Sprite target) {
                if (type == BaseBullet.ObjectType.ENEMY) {
                    if (mFcController != null) {
                        mFcController.addPlayerKill(1);
                        mFcController.addPlayerPoint(1);
                        if (mFcController.getPlayerKills() >= MAX_TANKS) {
                            win();
                        }
                    }
                } else if (type == BaseBullet.ObjectType.HEART) {
                    lose();
                }

            }
        });
        player.setTag(mSpriteTag.PLAYER_TANK_START);
        attachChild(player);
        mCamera.setChaseEntity(player);
    }

    private void createController() {
        if (mFcController == null) {
            mFcController = new FcController(
                    0,
                    CamerasManager.SCREEN_RESOLUTION.y
                            - mCharacterResources.mFcControllerBaseTextureRegion
                                    .getHeight(), mCamera,
                    mCharacterResources.mFcControllerBaseTextureRegion,
                    mCharacterResources.mFcControllerKnobTextureRegion, 0.1f,
                    mActivity, mOnControlListener);

            mFcController.getControlBase().setAlpha(0.15f);
            mFcController.getControlKnob().setAlpha(0.25f);
            mFcController.setButtonsAlpha(0.15f);
            mFcController.refreshControlKnobPosition();
            mFcController.setButtonAOnClickListener(mOnButtonClickListner);
            mFcController.setButtonBOnClickListener(mOnButtonClickListner);
            mFcController
                    .setButtonAPressHoldListener(mOnButtonAPressHoldListener);
            final PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            mFcController.setPlayerLives(player.mData.getLives());

            mCamera.setHUD(mFcController);
        }
    }

    private int mTankCounter = 0;

    private void initTanks(List<EnemyTank1> tanks) {
        if (tanks == null || tanks.size() == 0)
            return;

        final int size = mStageResources.mObjTankList.size();
        for (EnemyTank1 tank : tanks) {
            tank.setAutoFire(true);
            tank.setBirthTile(mTankCounter++ % size);
            tank.setOnDestroyTargetListener(new IOnDestroyTargetListener() {
                @Override
                public void onTargetDestroyed(int type, Sprite target) {
                    if (type == BaseBullet.ObjectType.HEART) {
                        lose();
                    }
                }
            });
            Log.d(TAG, "already show " + mTankCounter + " tanks");
        }
    }

    private void createTanksGenerator() {
        mTankPools = TankPools.getInstance(ScenesManager.SCENE_STAGE_1);
        mTankPools.initTanks(mActivity, MAX_TANKS);

        mTankFirstTimeGenerator = new TimerHandler(FIRST_TIME_COME_OUT_ELAPSED,
                new ITimerCallback() {
                    @Override
                    public void onTimePassed(TimerHandler pTimerHandler) {
                        unregisterUpdateHandler(pTimerHandler);
                        initTanks(mTankPools
                                .pullOutTanks(NUMBER_COME_OUT_PER_TIME));

                        mTankGenerator = new TimerHandler(COME_OUT_FREQUENCY,
                                true, new ITimerCallback() {
                                    @Override
                                    public void onTimePassed(
                                            TimerHandler pTimerHandler) {
                                        initTanks(mTankPools
                                                .pullOutTanks(NUMBER_COME_OUT_PER_TIME));
                                    }
                                });
                        registerUpdateHandler(mTankGenerator);
                    }
                });
        registerUpdateHandler(mTankFirstTimeGenerator);
    }

    public SpriteTag getSpriteTag() {
        return mSpriteTag;
    }

    private IOnScreenControlListener mOnControlListener = new IOnScreenControlListener() {
        @Override
        public void onControlChange(BaseOnScreenControl pBaseOnScreenControl,
                float pValueX, float pValueY) {
            PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            if (player == null || !player.isVisible()) {
                return;
            }

            if (pValueY == -1) {
                player.setRotation(PlayerTank1.FACE_UP);
            } else if (pValueY == 1) {
                player.setRotation(PlayerTank1.FACE_DOWN);
            } else if (pValueX == 1) {
                player.setRotation(PlayerTank1.FACE_RIGHT);
            } else if (pValueX == -1) {
                player.setRotation(PlayerTank1.FACE_LEFT);
            }

            int speed = player.mData.getMoveSpeed();
            if (player != null) {
                player.setVelocity(pValueX * speed, pValueY * speed);
            }
        }
    };

    private ButtonSprite.OnClickListener mOnButtonClickListner = new ButtonSprite.OnClickListener() {
        @Override
        public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX,
                float pTouchAreaLocalY) {
            PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            if (player == null || !player.isVisible()) {
                return;
            }

            if (pButtonSprite == mFcController.getButtonA()) {
                if (player != null) {
                    player.fire();
                }
            } else if (pButtonSprite == mFcController.getButtonB()) {
            }
        }
    };

    private IOnButtonPressHoldListener mOnButtonAPressHoldListener = new IOnButtonPressHoldListener() {

        @Override
        public void onPressedTrigger() {
            PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            if (player == null || !player.isVisible()) {
                return;
            }

            player.levelUpEnergy();
        }

        @Override
        public void onPressed() {
            PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            if (player == null || !player.isVisible()) {
                return;
            }

            player.levelUpEnergy();
        }

        @Override
        public void onPressedDone() {
            PlayerTank1 player = (PlayerTank1) getChildByTag(mSpriteTag.PLAYER_TANK_START);
            if (player == null || !player.isVisible()) {
                return;
            }

        }

    };

    @Override
    public boolean onKeyDown(final int pKeyCode, final KeyEvent pEvent) {
        Log.d(TAG,
                "onKeyDown keycode = " + pKeyCode + ", action = "
                        + pEvent.getAction());

        if ((pKeyCode == KeyEvent.KEYCODE_MENU || pKeyCode == KeyEvent.KEYCODE_BACK)
                && pEvent.getAction() == KeyEvent.ACTION_DOWN) {
            if (getChildScene() == mMenuScene) {
                back();
            } else {
                setChildScene(mMenuScene, false, true, true);
            }
            return true;
        } else {
            return false;
        }
    }

    private void win() {
        runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
                mFcController.hideController();
                mFcController.showWin();
                stopBackgroundMusic();
                startWinMusic();
                unregisterUpdateHandler(mTankFirstTimeGenerator);
                unregisterUpdateHandler(mTankGenerator);
            }
        });
    }

    private void lose() {
        runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
                mFcController.hideController();
                mFcController.showLose();
                stopBackgroundMusic();
                unregisterUpdateHandler(mTankFirstTimeGenerator);
                unregisterUpdateHandler(mTankGenerator);
            }
        });
    }

    private void startWinMusic() {
        mStageResources.mMusicWin.play();
    }

    private void startBackgroundMusic() {
        mStageResources.mMusicBackground.setLooping(true);
        mStageResources.mMusicBackground.play();
    }

    private void stopBackgroundMusic() {
        if (!mStageResources.mMusicBackground.isReleased()
                && mStageResources.mMusicBackground.isPlaying()) {
            mStageResources.mMusicBackground.stop();
        }
    }

    public void recycleTMXTileSprite(final Sprite tileSprite) {
        runOnUpdateThread(new Runnable() {
            @Override
            public void run() {
                tileSprite.setVisible(false);
                tileSprite.setIgnoreUpdate(true);
            }
        });
    }

    public void transformToGroundTile(Sprite tileSprite) {
        final TMXLayer layer = mStageResources.mMap.getTMXLayers().get(0);
        TMXTile tile = layer.getTMXTileAt(
                tileSprite.getX() + tileSprite.getWidth() / 2,
                tileSprite.getY() + tileSprite.getHeight() / 2);

        mStageResources.mTrafficLaw.updateTileType(tile, false);

        // if set groundId its property type value will change to ground
        tile.setGlobalTileID(mStageResources.mMap, mStageResources.mGroundId);
        layer.setIndex(tile.getTileRow()
                * mStageResources.mMap.getTileColumns() + tile.getTileColumn());
        layer.drawWithoutChecks(mStageResources.mGroundTextureRegion,
                tile.getTileX(), tile.getTileY(), tile.getTileWidth(),
                tile.getTileHeight(), Color.WHITE_ABGR_PACKED_FLOAT);
        layer.submit();
    }

    private void disposeSprites() {
        // dispose heart
        Sprite heart = (Sprite) getChildByTag(mSpriteTag.HEART);
        if (heart != null) {
            heart.dispose();
        }

        // dispose obstacles
        for (int i = mSpriteTag.OBSTACLE_START; i <= mSpriteTag.OBSTACLE_END; i++) {
            Sprite obstacle = (Sprite) getChildByTag(i);
            if (obstacle == null) {
                continue;
            } else {
                obstacle.dispose();
            }
        }

        // dispose walls
        for (int i = mSpriteTag.WALL_START; i <= mSpriteTag.WALL_END; i++) {
            WallTerrain wall = (WallTerrain) getChildByTag(i);
            if (wall == null) {
                continue;
            } else {
                wall.dispose();
            }
        }

        // dispose items
        if (mItemPools != null) {
            mItemPools.free();
        }

        // dispose enemy
        if (mTankPools != null) {
            mTankPools.free();
        }

        // dispose bullet
        if (mBulletPools != null) {
            mBulletPools.free();
        }

        // dispose player
        for (int i = mSpriteTag.PLAYER_TANK_START; i <= mSpriteTag.PLAYER_TANK_END; i++) {
            PlayerTank1 player = (PlayerTank1) getChildByTag(i);
            if (player != null) {
                player.free();
            } else {
                continue;
            }
        }

    }

    @Override
    public void resetScene() {
        disposeScene();
        mStageResources.reloadMusic(ResourcesManager.RESOURCES_STAGE_1);
        mStageResources.reloadMap(ResourcesManager.RESOURCES_STAGE_1);
        populateScene();
    }

    @Override
    public void disposeScene() {
        stopBackgroundMusic();
        clearUpdateHandlers();
        clearChildScene();

        detachChildren();
        disposeSprites();

        mFcController.disposeController();
        mFcController = null;
    }

    private void runOnUpdateThread(Runnable run) {
        mActivity.runOnUpdateThread(run);
    }

    public BaseStageResources getSceneResources() {
        return mStageResources;
    }
}
